// scalaxx - Scala XML eXtensions
// (c) 2009, Normen Müller, normen.mueller@gmail.com
// $Id: XPath.scala 50 2009-11-12 11:37:31Z normen.mueller $
package scalaxx.path

/** This object provides a simple interface to the XPath1.0 parser class.
 *
 * <dl>
 *   <dt>Union</dt><dd>Constructor of XPath union expressions</dd>
 *   <dt>Root</dt><dd>Constructor of XPath root expressions</dd>
 *   <dt>Path</dt><dd>Constructor of XPath location expressions</dd>
 *   <dt>Filter</dt><dd>Constructor of XPath filter expressions</dd>
 *   <dt>Axis</dt><dd>Constructor of XPath axis expressions</dd>
 *   <dt>*</dt><dd>Asterisk</dd>
 *   <dt>Type</dt><dd>Constructor of XPath node type expressions</dd>
 *   <dt>NCName</dt><dd>Constructor of XPath NCName expressions</dd>
 *   <dt>QName</dt><dd>Constructor of XPath QName expressions</dd>
 *   <dt>Fun</dt><dd>Constructor of XPath function expressions</dd>
 *   <dt>Val</dt><dd>Constructor of XPath value (string, number) expressions</dd>
 *   <dt>Var</dt><dd>Constructor of XPath variable reference expressions</dd> *   
 * </dl>
 * 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.2 */
object XPath extends Parser {
  
  def parse(input: String) = {
    val expand = (in: String) => input.trim replaceAll ("//", "/descendant-or-self::node()/") replaceAll ("@", "attribute::") replaceAll ("""\.\.""", "parent::node()") replaceAll
      //@todo `.' should be handled better
      ("""\./""", "self::node()/") replaceAll ("""\.,""", "self::node(),") replaceAll ("""\.=""", "self::node()=") replaceAll ("""[\.]$""", "self::node()")
    phrase(expr)(new lexical.Scanner(expand(input))) match {
      case Success(result, _) => result
      case n@_                => throw new Exception("XPath parsing error: " + n)
    }
  }
  
}

/**
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.2 */
sealed abstract class Expr {
  def flush(attrs: List[Expr]) = (new StringBuilder /: attrs)((b,a) => b append "[" append a append "]") 
}

/**
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.2 */
sealed abstract class LocationPath extends Expr {
  /* child axis */
  lazy val children = this match {
    case Path(s) => Path(s ::: List(Axis("child", All, Nil)))
    case Root(s) => Root(s ::: List(Axis("child", All, Nil)))
    case _       => this
  }
  
  def childAt(pos: Int) = this match {
    case Path(s) => Path(s ::: List(Axis("child", All, List(NVal(pos.toDouble)))))
    case Root(s) => Root(s ::: List(Axis("child", All, List(NVal(pos.toDouble)))))
    case _       => this
  }
  
  /* parent axis */
  lazy val parent = this match {
    case Path(s) => Path(s slice (0, s.length - 1))
    case Root(s) => Root(s slice (0, s.length - 1))
    case _       => this
  }
  
  /* attribute axis */
  lazy val attributes = this match {
    case Path(s) => Path(s ::: List(Axis("attribute", All, Nil)))
    case Root(s) => Root(s ::: List(Axis("attribute", All, Nil)))
    case _       => this
  }
  
  def attributeAt(position: Int) = this match {
    case Path(s) => Path(s ::: List(Axis("attribute", All, List(NVal(position.toDouble)))))
    case Root(s) => Root(s ::: List(Axis("attribute", All, List(NVal(position.toDouble)))))
    case _       => this
  }
  
  /* text node */
  lazy val text = this match {
    case Path(s) => Path(s ::: List(Axis("child", Type("text"), Nil)))
    case Root(s) => Root(s ::: List(Axis("child", Type("text"), Nil)))
    case _       => this
  }
  
  def ++ = this match {
    case Path(s) => Path(inc(s))
    case Root(s) => Root(inc(s))
    case _       => this
  }
  
  private def inc(s: List[Expr]) = 
    if(s isEmpty) s
    else s.last match {
      case Axis("child", All, List(NVal(p)))     => (s slice (0, s.length - 1)) ++ List(Axis("child", All, List(NVal(p + 1))))
      case Axis("attribute", All, List(NVal(p))) => (s slice (0, s.length - 1)) ++ List(Axis("attribute", All, List(NVal(p + 1))))
      case _                       => s
    }
  
  def -- = this match {
    case Path(s) => Path(dec(s))
    case Root(s) => Root(dec(s))
    case _       => this
  }
  
  private def dec(s: List[Expr]) = 
    if(s isEmpty) s
    else s.last match {
      case Axis("child", All, List(NVal(p))) =>
        (s slice (0, s.length - 1)) ++ List(Axis("child", All, List(NVal({if(p > 1) p - 1 else p}))))
      case Axis("attribute", All, List(NVal(p))) =>
        (s slice (0, s.length - 1)) ++ List(Axis("attribute", All, List(NVal({if(p > 1) p - 1 else p}))))
      case _ => s
    }
}

case object Root extends Root(Nil)
case class Root(steps: List[Expr]) extends LocationPath { override def toString = steps mkString ("/", "/", "") }
case class Path(steps: List[Expr]) extends LocationPath { override def toString = steps mkString "/" }

trait NodeTest
case class Filter(expr: Expr, attrs: List[Expr]) extends Expr { override def toString = expr.toString + flush(attrs) }
case class Axis(val name: String, val ident: NodeTest, val attrs: List[Expr]) extends Expr { override def toString = name + "::" + ident + flush(attrs) }
case object All extends Expr with NodeTest { override def toString = "*" }
case class Fun(name: String, args: List[Expr]) extends Expr { override def toString = name + (args mkString ("(", ", ", ")")) }
case class Type(override val name: String) extends Fun(name, Nil) with NodeTest
case class NCName(local: String) extends Expr with NodeTest { override def toString = local }
case class QName(prefix: String, local: String) extends Expr with NodeTest { override def toString = prefix+":"+local }
case class $(v: String) extends Expr { override def toString = "$" + v }

sealed abstract class Val extends Expr
case class NVal(v: Double) extends Val  { override def toString = v.toString}
case class TVal(v: String) extends Val { override def toString = v mkString ("\"", "", "\"") }

sealed abstract class Unary(right: Expr) extends Expr {
  val symbol: String
  override def toString = symbol + " " + right
}
case class Negative(right: Expr) extends Unary(right) { val symbol = "-" }

sealed abstract class Binary(left: Expr, right: Expr) extends Expr {
  val symbol: String
  override def toString = "("+left+" "+symbol+" "+right+")"
}
case class Union(left: Expr, right: Expr) extends Binary(left, right) { val symbol = "|" }
case class Or(left: Expr, right: Expr) extends Binary(left, right) { val symbol = "or" } 
case class And(left: Expr, right: Expr) extends Binary(left, right) { val symbol = "and" }
case class Equal(left: Expr, right: Expr) extends Binary(left, right) { val symbol = "=" }
case class Unequal(left: Expr, right: Expr) extends Binary(left, right) { val symbol = "!=" }
case class LTE(left: Expr, right: Expr) extends Binary(left, right) { val symbol = "<=" }
case class LT(left: Expr, right: Expr) extends Binary(left, right) { val symbol = "<" }
case class GTE(left: Expr, right: Expr) extends Binary(left, right) { val symbol = ">=" }
case class GT(left: Expr, right: Expr) extends Binary(left, right) { val symbol = ">" }
case class Plus(left: Expr, right: Expr) extends Binary(left, right) { val symbol = "+" }
case class Minus(left: Expr, right: Expr) extends Binary(left, right) { val symbol = "-" }
case class Mult(left: Expr, right: Expr) extends Binary(left, right) { val symbol = "*" }
case class Div(left: Expr, right: Expr) extends Binary(left, right) { val symbol = "div" }
case class Mod(left: Expr, right: Expr) extends Binary(left, right) { val symbol = "mod" }
